package com.itaming.lycheeframework.redis.config;

import com.itaming.lycheeframework.redis.properties.LycheeRedisProperties;
import com.itaming.lycheeframework.redis.serializer.SerializerFactory;
import com.itaming.lycheeframework.redis.service.RedisService;
import com.itaming.lycheeframework.support.properties.ExtendedPropertySource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Redis模块配置类
 *
 * @author A.Ming
 */
@AutoConfiguration(before = {RedisAutoConfiguration.class})
@EnableConfigurationProperties({LycheeRedisProperties.class})
@ExtendedPropertySource({"classpath:lychee-redis.yml"})
@RequiredArgsConstructor
public class LycheeRedisConfiguration {

    /**
     * Redis配置参数
     */
    private final LycheeRedisProperties properties;

    /**
     * RedisTemplate
     */
    @Bean
    @ConditionalOnMissingBean(name = {"redisTemplate"})
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 创建RedisTemplate实例
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        // 设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);

        // 设置key序列化器
        RedisSerializer<?> keySerializer = SerializerFactory.getKeySerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        // 设置value序列化器
        RedisSerializer<?> valueSerializer = SerializerFactory.getValueSerializer(properties.getSerializationMode());
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        return redisTemplate;
    }

    /**
     * RedisService
     */
    @Bean
    public RedisService redisService(@Qualifier("redisTemplate") RedisTemplate<?, ?> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        return new RedisService(createInternalTemplate(redisTemplate), stringRedisTemplate);
    }

    /**
     * 自定义缓存配置
     * <p>
     * 替换默认缓存配置中的序列化器为 redisTemplate 的序列化器，以保证序列化的一致性
     */
    @Bean
    // 通过@EnableCaching开启了缓存时生效
    @ConditionalOnBean(CacheAspectSupport.class)
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties, @Qualifier("redisTemplate") RedisTemplate<?, ?> redisTemplate) {
        // 创建redis缓存配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

        // 设置value序列化器
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));

        // 根据Spring的CacheProperties设置缓存参数
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }

        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }

        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }

        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }

        return config;
    }

    /**
     * 基于已有的 {@code RedisTemplate} 创建一个新的 {@code <String, Object>} 类型的 {@code RedisTemplate}
     *
     * <p>
     * 备注：
     * <p>
     * RedisService 需要依赖 {@code RedisTemplate<String, Object>} 来实现常用操作。
     * 虽然框架默认提供了一个 {@code RedisTemplate<String, Object>}，但允许项目中自定义 {@code RedisTemplate}。
     * 我们无法保证项目中自定义的 {@code RedisTemplate} 一定是 {@code <String, Object>} 类型的。
     * <p>
     * 为了兼容项目中自定义的 {@code RedisTemplate}，
     * 通过已有的 {@code RedisTemplate} 来创建一个新的内部专用 {@code RedisTemplate}。
     * 对于 {@code HashKey}、{@code Value} 和 {@code HashValue}
     * 则复用传入 {@code RedisTemplate} 的序列化器，从而继承项目中自定义的序列化策略。
     *
     * @param redisTemplate RedisTemplate
     * @return 新创建的 {@code RedisTemplate}
     */
    private RedisTemplate<String, Object> createInternalTemplate(RedisTemplate<?, ?> redisTemplate) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisTemplate.getConnectionFactory());
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(redisTemplate.getHashKeySerializer());
        template.setValueSerializer(redisTemplate.getValueSerializer());
        template.setHashValueSerializer(redisTemplate.getHashValueSerializer());
        template.afterPropertiesSet();
        return template;
    }

}
